package com.yanyu.space.codegen.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSONObject;
import com.yanyu.space.codegen.bean.constant.GeneratorConstant;
import com.yanyu.space.codegen.bean.enums.NamingStrategy;
import com.yanyu.space.codegen.bean.po.Attr;
import com.yanyu.space.codegen.bean.po.Template;
import com.yanyu.space.codegen.bean.vo.attr.TemplateAttr;
import com.yanyu.space.codegen.bean.vo.code.GeneratorCodeSearchVo;
import com.yanyu.space.codegen.bean.vo.dssetting.ColumnInfoVo;
import com.yanyu.space.codegen.bean.vo.dssetting.TableInfoVo;
import com.yanyu.space.codegen.dao.TemplateDescMapper;
import com.yanyu.space.codegen.dao.TemplateMapper;
import com.yanyu.space.codegen.util.strategy.ICustomAttrStrategy;
import com.yanyu.space.common.core.exception.ServiceException;
import com.yanyu.space.common.core.util.AppContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.springframework.core.io.ClassPathResource;
import sun.nio.ch.IOUtil;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成工具类
 *
 * @author yanyu
 * @date 2020/12/1
 */
@Slf4j
public class GeneratorCodeUtil {

    private static TemplateDescMapper templateDescMapper;
    private static final Properties PROPERTIES = new Properties();
    private static final Map<String, ICustomAttrStrategy> customAttrStrategyMap = new HashMap<>();

    public static void init(Map<String, ICustomAttrStrategy> customAttrStrategyMap, TemplateDescMapper templateDescMapper) {
        try {
            ClassPathResource resource = new ClassPathResource("generator.properties");
            PROPERTIES.load(resource.getInputStream());
            if(MapUtil.isNotEmpty(customAttrStrategyMap)) {
                GeneratorCodeUtil.customAttrStrategyMap.putAll(customAttrStrategyMap);
            }
            GeneratorCodeUtil.templateDescMapper = templateDescMapper;
        } catch (IOException e) {
            log.error("载入generator.properties资源失败", e);
        }
    }

    /**
     * 生成模板信息
     *
     * @param templates     模板信息
     * @param vo            请求信息
     * @param tableInfoVo   表
     * @param columnInfoVos 表列
     * @param customAttrs   自定义属性
     * @param zip           文件压缩
     */
    public static void generator(List<Template> templates,
                                 GeneratorCodeSearchVo vo,
                                 TableInfoVo tableInfoVo,
                                 List<ColumnInfoVo> columnInfoVos,
                                 List<Attr> customAttrs,
                                 ZipOutputStream zip) {

        // 1. 设置变量
        TemplateAttr templateAttr = new TemplateAttr();
        templateAttr.setJavaPackageName(vo.getJavaPackageName());
        templateAttr.setJavaModuleName(vo.getJavaModuleName());
        String javaBeanName = tableInfoVo.getTableName();
        if (StringUtils.isNotBlank(vo.getIngoreTablePrefix())) {
            javaBeanName = StringUtil.removePrefix(javaBeanName, vo.getIngoreTablePrefix());
        }
        if (NamingStrategy.UNDERLINE_TO_CAMEL.getValue().equals(vo.getTableNaming())) {
            javaBeanName = StringUtil.lineToCamel(javaBeanName);
        }
        templateAttr.setJavaBeanName(StringUtil.capitalFirst(javaBeanName));
        templateAttr.setLowJavaBeanName(StringUtils.isNotBlank(javaBeanName) ? javaBeanName.substring(0, 1).toLowerCase() + javaBeanName.substring(1) : "");
        templateAttr.setAuthor(vo.getAuthor());
        templateAttr.setDate(DateFormatUtils.format(new Date(), "yyyy-MM-dd"));
        templateAttr.setTableDesc(StringUtil.defaultIfBlack(tableInfoVo.getTableComment()));
        templateAttr.setTableName(tableInfoVo.getTableName());
        templateAttr.setTableColumns(columnInfoVos.stream().filter(columnInfoVo ->
                !CollectionUtils.containsAny(vo.getIgnoreColumnNames(), StringUtil.defaultIfBlack(columnInfoVo.getColumnName()))
        ).map(columnInfoVo -> {
            String columnName = StringUtil.defaultIfBlack(columnInfoVo.getColumnName());
            return templateAttr.buildTableColumns(
                    StringUtil.defaultIfBlack(PROPERTIES.getProperty(columnInfoVo.getColumnType())),
                    NamingStrategy.UNDERLINE_TO_CAMEL.getValue().equals(vo.getColumnNaming()) ? StringUtil.lineToCamel(columnName) : columnName,
                    StringUtil.defaultIfBlack(columnInfoVo.getColumnComment()),
                    columnInfoVo.getColumnName()
            );
        }).collect(Collectors.toList()));
        customAttrs.parallelStream().forEach(customAttr -> {
            String attrName = customAttr.getName();
            String attrValue = customAttr.getValue();
            if(StringUtils.startsWith(attrValue, "@") && StringUtils.endsWith(attrValue, "@")) {
                attrValue = StringUtils.substringAfter(attrValue, "@");
                attrValue = StringUtils.substringBeforeLast(attrValue, "@");
                ICustomAttrStrategy customAttrStrategy = customAttrStrategyMap.get(StringUtils.join(attrValue, "CustomAttrStrategy"));
                templateAttr.appendCustomAttrs(attrName, customAttrStrategy.getAttrName(templateAttr));
            } else {
                templateAttr.appendCustomAttrs(attrName, attrValue);
            }
        });

        //  2. Velocity引擎设置参数
        Map param = JSONObject.parseObject(JSONObject.toJSONString(templateAttr), Map.class);
        if (CollectionUtils.isNotEmpty(customAttrs)) {
            for (Attr customAttr : customAttrs) {
                Map<String, String> value = new HashMap<String, String>(3);
                value.put("name", customAttr.getName());
                value.put("value", customAttr.getValue());
                value.put("desc", customAttr.getRemark());
                param.putIfAbsent(customAttr.getName(), value);
            }
        }
        VelocityContext ctx = new VelocityContext(param);

        // 3. 处理模板
        for (Template template : templates) {
            //渲染模板
            VelocityEngine ve = new VelocityEngine();
            ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "ds");
            ve.setProperty("ds.resource.loader.instance", new DbResourceLoader(templateDescMapper));
            org.apache.velocity.Template tpl = ve.getTemplate(String.valueOf(template.getId()), "UTF-8");
            StringWriter sw = new StringWriter();
            tpl.merge(ctx, sw);

            try {
                //添加到zip
                zip.putNextEntry(new ZipEntry(getFileName(templateAttr, template)));
                IoUtil.write(zip, "UTF-8", false, sw.toString());
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException e) {
                log.error("渲染模板失败，表名：" + templateAttr.getTableName() + ",{}", e);
                throw new ServiceException();
            }
        }
    }

    private static String getFileName(TemplateAttr templateAttr, Template template) {
        StringBuilder fileName = new StringBuilder(StringUtils.join("main", File.separator, "java",
                File.separator, StringUtils.replace(templateAttr.getJavaPackageName(), ".", File.separator),
                File.separator, StringUtils.replace(templateAttr.getJavaModuleName(), ".", File.separator), File.separator));

        if (StringUtils.isNotBlank(template.getModuleName())) {
            String fullModuleName = StringUtils.replace(template.getModuleName(), GeneratorConstant.TEMPLATE_PATH_VARIABLE, templateAttr.getJavaBeanName());
            fullModuleName = StringUtils.replace(fullModuleName, GeneratorConstant.TEMPLATE_LOWER_PATH_VARIABLE, templateAttr.getLowJavaBeanName());
            fileName.append(StringUtils.replace(fullModuleName, ".", File.separator)).append(File.separator);
        }

        String fullJavaBeanName = StringUtils.replace(template.getName(), GeneratorConstant.TEMPLATE_PATH_VARIABLE, templateAttr.getJavaBeanName());
        fileName.append(fullJavaBeanName);
        return fileName.toString();
    }
}
